#include "threads.h"
#include "ai.h"
#include "events.h"
#include "gui.h"
#include "util.h"
#include <pthread.h>
#include <string.h>
#include <stdlib.h>

#ifdef WIN32
#include <windows.h>
#endif

extern Gui gui;

/* W module threads.c dozwolone jest wykonywanie tylko 1 wątku. */
/* Rozpoczęcie nowego powoduje przerwanie starego. */
static pthread_t thread;
static volatile int isThreadActive = FALSE;

/* odczekanie zadanej liczby milisekund */
static void thread_msleep(unsigned int milis) {

#ifdef WIN32

	Sleep(milis);

#else

	struct timespec czas;
	czas.tv_sec = 0;
	czas.tv_nsec = 1000*1000*milis;

	nanosleep(&czas, NULL);

#endif
	
}

/* Wyświetlenie okienka dialogowego o zadanym tekście */
/* msg jest łańcuchem utworzonym na stercie */
static gboolean thread_showmessage(char* msg) {

	GtkMessageDialog* dlg;

	gdk_threads_enter();

	dlg = (GtkMessageDialog*)gtk_message_dialog_new(gui.main, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO,
	                             GTK_BUTTONS_OK, msg);

	gtk_dialog_run(GTK_DIALOG(dlg));

	gtk_widget_destroy(GTK_WIDGET(dlg));

	free(msg);

	gdk_threads_leave();

	return FALSE;
	
}

/* Opakowanie dla funkcji show_winner, do wywoływania */
/* tylko wewnątrz tego modułu... */
static gboolean thread_show_winner() {

	gdk_threads_enter();

	show_winner();
	
	gdk_threads_leave();

	return FALSE;
	
}

/* Funkcja wykonuje pojedynczy ruch komputera */
static void* computer_make_move(void* data) {

	Move m;
	Board* b;
	Board* copy;

	/* asynchroniczne odwołanie wątku */
	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

	b = (Board*)data;
	copy = board_copy(b);

	m = ai_find_best_move(copy);

	board_cleanup(copy);

	board_play(b, m.x, m.y);

	/* zabezpieczamy wątki */
	gdk_threads_enter();
	refresh_display();
	gdk_threads_leave();

	/* jeśli posunięcie komputera kończy grę, ogłoś zwycięzcę */
	if(board_is_game_finished(b)) {
		gdk_threads_enter();
		g_idle_add(thread_show_winner, NULL);
		gdk_threads_leave();
	}

	isThreadActive = FALSE;

	return NULL;

}

/* Funkcja odpala całą grę komputer vs. komputer */
static void* computer_vs_computer(void* data) {

	const int MILISECONDS_DELAY = 10;
	
	Board* b;
	Board* copy;
	Move m;

	/* asynchroniczne odwołanie wątku */
	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

	b = (Board*)data;
	copy = board_copy(b);

	while(!board_is_game_finished(b)) {

		m = ai_find_best_move(copy);

		board_play(b, m.x, m.y);
		board_play(copy, m.x, m.y);

		/* zabezpieczamy wątki */
		gdk_threads_enter();
		refresh_display();
		gdk_threads_leave();

		thread_msleep(MILISECONDS_DELAY);

	}

	board_cleanup(copy);

	/* wyświetl info kto wygrał */
	gdk_threads_enter();
	g_idle_add(thread_show_winner, NULL);
	gdk_threads_leave();

	isThreadActive = FALSE;

	return NULL;

}

static void* hint(void* v) {

	Move m;
	int available_moves;
	Board* b;
	Board* copy;

	/* asynchroniczne odwołanie wątku */
	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

	b = (Board*)v;

	/* sprawdź liczbę pozostałych ruchów */
	available_moves = board_get_allowed_moves_count(b);

	gdk_threads_enter();
		gtk_statusbar_remove_all (gui.statusBar, 0);
		gtk_statusbar_push (gui.statusBar, 0, "Poczekaj...");
	gdk_threads_leave();

	/* blokujemy możliwość wykonywania ruchów */
	move_lock();
	
	/* znajdź najlepszy ruch */
	copy = board_copy(b);
	m = ai_find_best_move(copy);
	board_cleanup(copy);

	gdk_threads_enter();
		gtk_statusbar_remove_all (gui.statusBar, 0);
	gdk_threads_leave();

	move_unlock();

	/* jeśli wykonano jakiekolwiek ruchy, odpuszczamy, bo
	 * użytkownik zignorował wykonującego się hinta */
	if(board_get_allowed_moves_count(b) != available_moves) {
		
		isThreadActive = FALSE;
		return NULL;
		
	}
	else {
		
		board_play_move(b, m);

		/* odśwież */
		gdk_threads_enter();

		refresh_display();
		
		if(board_is_game_finished(b))
			g_idle_add(thread_show_winner, NULL);
	
		gdk_threads_leave();

		isThreadActive = FALSE;

		/* odpal ruch komputera, jeśli trzeba */
		if(!board_is_game_finished(b) && !board_is_current_move_human(b))
			thread_computer_make_move(b);
	
		return NULL;
		
	}
	
}

/* Przewidywanie wyniku */
static void* predict(void* data) {

	char* buffer;
	float result;
	symbol s;
	Board* b;
	Board* copy;
	
	/* asynchroniczne odwołanie wątku */
	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
	
	b = (Board*)data;

	s = board_get_current_player_symbol(b);

	/* Informujemy użytkownika żeby poczekał... */
	gdk_threads_enter();

	gtk_statusbar_remove_all (gui.statusBar, 0);

	gtk_statusbar_push (gui.statusBar, 0, "Poczekaj...");
	
	gdk_threads_leave();

	/* blokujemy możliwość wykonywania ruchów */
	move_lock();

	copy = board_copy(b);

	result = ai_predict_result(copy);

	board_cleanup(copy);

	move_unlock();

	buffer = malloc(100);

	if(s == CIRCLE) {
	
		if(result > 0.0f)
			sprintf(buffer, "Kółko wygra %.1f punktami.", result);
		else
			sprintf(buffer, "Krzyżyk wygra %.1f punktami.", -result);

	}
	else {

		if(result > 0.0f)
			sprintf(buffer, "Krzyżyk wygra %.1f punktami.", result);
		else
			sprintf(buffer, "Kółko wygra %.1f punktami.", -result);

	}

	gdk_threads_enter();

	gtk_statusbar_remove_all (gui.statusBar, 0);

	gdk_threads_leave();

	g_idle_add((GSourceFunc)thread_showmessage, buffer);

	isThreadActive = FALSE;

	return NULL;
	
}

/* Wykonaj ruch komputera w osobnym wątku */
void thread_computer_make_move(Board* b) {

	thread_interrupt();

	isThreadActive = TRUE;

	pthread_create(&thread, NULL, &computer_make_move, b);
	
}

/* Wykonaj symulację gry w osobnym wątku */
void thread_computer_vs_computer(Board* b) {

	thread_interrupt();

	isThreadActive = TRUE;

	pthread_create(&thread, NULL, &computer_vs_computer, b);

}

/* Przerwij działanie wątku */
void thread_interrupt() {

	/* przerwij poprzedni wątek */
	if(isThreadActive) {
		pthread_cancel(thread);
		isThreadActive = FALSE;
	}
	
}

/* Znajdź hint dla gracza */
void thread_hint(Board* b) {

	thread_interrupt();

	isThreadActive = TRUE;

	pthread_create(&thread, NULL, &hint, b);
	
}

/* Przewidź wynik gry */
void thread_predict(Board* b) {

	thread_interrupt();

	isThreadActive = TRUE;

	pthread_create(&thread, NULL, &predict, b);
	
}